package log

import (
	"context"
	"gitee.com/kinwyb/conv"
	"github.com/sirupsen/logrus"
	"github.com/spf13/pflag"
	"go.elastic.co/apm/module/apmlogrus"
	"io"
	"os"
	"strings"
	"time"
)

type WatchFun func(entry *logrus.Entry)

var watchLogChange []WatchFun //检测日志变化

// WatchLogChange 添加日志变化监视
func WatchLogChange(watchFun WatchFun) {
	watchLogChange = append(watchLogChange, watchFun)
}

var defaultJsonFormatter = logrus.JSONFormatter{
	TimestampFormat: "2006-01-02 15:04:05.999",
	FieldMap: logrus.FieldMap{
		logrus.FieldKeyTime:  "@timestamp",
		logrus.FieldKeyLevel: "@level",
		logrus.FieldKeyMsg:   "@message",
		logrus.FieldKeyFunc:  "@caller",
	},
}
var defaultTextFormatter = logrus.TextFormatter{
	DisableColors:   true,
	TimestampFormat: "2006-01-02 15:04:05.999",
}

var lg = logrus.New()
var stdPrint = &stdOut{}
var logEntry = lg.WithField("env", "dev")

var AppTool = Tag("appTool")

func init() {
	lg.SetOutput(&nullWriter{})
	stdPrint.level = lg.Level
	lg.AddHook(stdPrint)
}

// GetLogger 获取日志对象
func GetLogger() *logrus.Logger {
	return lg
}

// SetElasticAPMHook 设置Elastic APM Hook
func SetElasticAPMHook() {
	lg.AddHook(&apmlogrus.Hook{})
}

// SetLogFile 设置日志输出到文件，only是否只是输出到文件
func SetLogFile(filepath string, level logrus.Level, only bool) {
	lg.SetLevel(level)
	if only {
		removeHook(stdPrint)
		toFile(filepath, 7)
	} else {
		hookToFile(filepath, 7)
	}
}

func SetLevel(level logrus.Level) {
	removeHook(stdPrint)
	stdPrint.level = level
	lg.AddHook(stdPrint)
}

// RemoveStdOut 移除终端日志输出
func RemoveStdOut() {
	removeHook(stdPrint)
}

// SetWriter 设置日志输出
func SetWriter(w io.Writer) {
	if w != nil {
		lg.SetLevel(stdPrint.level)
		lg.SetOutput(w)
	}
}

// Tag 设置tag
func Tag(tag string) *logrus.Entry {
	return logEntry.WithField("tag", tag)
}

// SetLogEnv 设置日志环境
func SetLogEnv(env string, appname ...string) {
	if len(appname) > 0 && appname[0] != "" {
		logEntry = lg.WithField("app", appname[0]).WithField("env", env)
	} else {
		logEntry = lg.WithField("env", env)
	}
	AppTool = Tag("appTool")
	for _, v := range watchLogChange {
		if v != nil {
			v(logEntry)
		}
	}
}

// WithField Adds a field to the log entry, note that it doesn't log until you call
// Debug, Print, Info, Warn, Error, Fatal or Panic. It only creates a log entry.
// If you want multiple fields, use `WithFields`.
func WithField(key string, value interface{}) *logrus.Entry {
	return logEntry.WithField(key, value)
}

// WithFields Adds a struct of fields to the log entry. All it does is call `WithField` for
// each `Field`.
func WithFields(fields logrus.Fields) *logrus.Entry {
	return logEntry.WithFields(fields)
}

// WithError Add an error as single field to the log entry.  All it does is call
// `WithError` for the given `error`.
func WithError(err error) *logrus.Entry {
	return logEntry.WithError(err)
}

// WithContext Add a context to the log entry.
func WithContext(ctx context.Context) *logrus.Entry {
	return logEntry.WithContext(ctx)
}

// WithTime Overrides the time of the log entry.
func WithTime(t time.Time) *logrus.Entry {
	return logEntry.WithTime(t)
}

var logToConsole = true

func InitLog(level logrus.Level, toFile bool, fileName string) {
	ToElasticsearch(fileName, level)
	SetLevel(level)
	if toFile {
		//设置日志
		SetLogFile("./logs/"+fileName+".log", level, !logToConsole)
	}
}

// SetLogFlag 解析elastic配置
func SetLogFlag(flag *pflag.FlagSet) {
	defaultLogEsHost := strings.TrimSpace(os.Getenv("LOG_ELASTIC_HOST"))
	flag.StringVar(&logEsHost, "log_elastic", defaultLogEsHost, "日志elastic地址,环境变量[LOG_ELASTIC_HOST][eg http://user:pwd@127.0.0.1:9200]")
	// 日志输出到控制台
	consoleLogEnv := strings.ToUpper("LOG_TO_CONSOLE")
	consoleLogEnv = strings.ReplaceAll(consoleLogEnv, "-", "_")
	defaultConsoleLogStr := strings.TrimSpace(os.Getenv(consoleLogEnv))
	defaultConsoleLog := conv.ToBoolDefault(defaultConsoleLogStr, true)
	flag.BoolVar(&logToConsole, "log_to_console", defaultConsoleLog, "日志输出到控制台,环境变量["+consoleLogEnv+"]")
}

// 移除
func removeHook(h logrus.Hook) {
	newHooks := logrus.LevelHooks{}
	for _, hooks := range lg.Hooks[logrus.PanicLevel] {
		if hooks != h {
			newHooks.Add(hooks)
		}
	}
	lg.ReplaceHooks(newHooks)
}

type nullWriter struct {
}

func (n2 nullWriter) Write(p []byte) (n int, err error) {
	return 0, nil
}

type stdOut struct {
	level logrus.Level
}

func (s *stdOut) Levels() []logrus.Level {
	var lvs []logrus.Level
	for _, v := range logrus.AllLevels {
		if s.level >= v {
			lvs = append(lvs, v)
		}
	}
	return lvs
}

func (s *stdOut) Fire(entry *logrus.Entry) error {
	msg, err := entry.String()
	if err != nil {
		return err
	}
	_, err = os.Stdout.WriteString(msg)
	return err
}
